#include "sdk.h"
#include "addtododlg.h"
#include "asktypedlg.h"
#include "M_Code_StyledTextCtrl.h"
#include "editorcolourset.h"
#include "todolist.h"
#include "todolistview.h"
#include "todosettingsdlg.h"
#include <wx/arrimpl.cpp>

// TODO (m#1#2019年08月15日): 改善自动刷新事务列表算法


WX_DEFINE_OBJARRAY( ToDoItems );

namespace {
  PluginRegistrant<ToDoList> reg( _T( "ToDoList" ) );
}

const int idViewTodo = XRCID( "idViewTodo" );
const int idAddTodo = wxNewId();

BEGIN_EVENT_TABLE( ToDoList, M_Code_Plugin )
  EVT_UPDATE_UI( idViewTodo, ToDoList::OnUpdateUI )
  EVT_MENU( idViewTodo, ToDoList::OnViewList )
  EVT_UPDATE_UI( idAddTodo, ToDoList::OnUpdateAdd )
  EVT_MENU( idAddTodo, ToDoList::OnAddItem )
END_EVENT_TABLE()

ToDoList::ToDoList() :
  m_InitDone( false ),
  m_ParsePending( false ) {
  if( !Manager::LoadResource( _T( "ToDoList.zip" ) ) ) {
    NotifyMissingFile( _T( "ToDoList.zip" ) );
  }
}

ToDoList::~ToDoList() {
}

void ToDoList::OnAttach() {
  wxArrayString titles;
  wxArrayInt widths;
  titles.Add( _T( "类型" ) );
  widths.Add( 64 );
  titles.Add( _T( "内容" ) );
  widths.Add( 320 );
  titles.Add( _T( "用户" ) );
  widths.Add( 64 );
  titles.Add( _T( "级别" ) );
  widths.Add( 48 );
  titles.Add( _T( "行" ) );
  widths.Add( 48 );
  titles.Add( _T( "时间" ) );
  widths.Add( 56 );
  titles.Add( _T( "文件" ) );
  widths.Add( 640 );
  m_pListLog = new ToDoListView( titles, widths, m_Types );
  LogManager* msgMan = Manager::Get()->GetLogManager();
  m_ListPageIndex = msgMan->SetLog( m_pListLog );
  msgMan->Slot( m_ListPageIndex ).title = _T( "事务" );
  CodeBlocksLogEvent evt( cbEVT_ADD_LOG_WINDOW, m_pListLog, msgMan->Slot( m_ListPageIndex ).title, msgMan->Slot( m_ListPageIndex ).icon );
  Manager::Get()->ProcessEvent( evt );
  m_AutoRefresh = Manager::Get()->GetConfigManager( _T( "todo_list" ) )->ReadBool( _T( "auto_refresh" ), true );
  LoadUsers();
  LoadTypes();
  Manager::Get()->RegisterEventSink( cbEVT_APP_STARTUP_DONE, new cbEventFunctor<ToDoList, CodeBlocksEvent>( this, &ToDoList::OnAppDoneStartup ) );
  Manager::Get()->RegisterEventSink( cbEVT_EDITOR_OPEN, new cbEventFunctor<ToDoList, CodeBlocksEvent>( this, &ToDoList::OnReparseCurrent ) );
  Manager::Get()->RegisterEventSink( cbEVT_EDITOR_SAVE, new cbEventFunctor<ToDoList, CodeBlocksEvent>( this, &ToDoList::OnReparseCurrent ) );
  Manager::Get()->RegisterEventSink( cbEVT_EDITOR_ACTIVATED, new cbEventFunctor<ToDoList, CodeBlocksEvent>( this, &ToDoList::OnReparseCurrent ) );
  Manager::Get()->RegisterEventSink( cbEVT_EDITOR_CLOSE, new cbEventFunctor<ToDoList, CodeBlocksEvent>( this, &ToDoList::OnReparseCurrent ) );
  Manager::Get()->RegisterEventSink( cbEVT_PROJECT_CLOSE, new cbEventFunctor<ToDoList, CodeBlocksEvent>( this, &ToDoList::OnReparse ) );
  Manager::Get()->RegisterEventSink( cbEVT_PROJECT_ACTIVATE, new cbEventFunctor<ToDoList, CodeBlocksEvent>( this, &ToDoList::OnReparse ) );
  Manager::Get()->RegisterEventSink( cbEVT_PROJECT_FILE_ADDED, new cbEventFunctor<ToDoList, CodeBlocksEvent>( this, &ToDoList::OnReparse ) );
  Manager::Get()->RegisterEventSink( cbEVT_PROJECT_FILE_REMOVED, new cbEventFunctor<ToDoList, CodeBlocksEvent>( this, &ToDoList::OnReparse ) );
}

void ToDoList::OnRelease( M_Code_unused bool appShutDown ) {
  CodeBlocksLogEvent evt( cbEVT_REMOVE_LOG_WINDOW, m_pListLog );
  m_pListLog->DestroyControls( false );
  Manager::Get()->ProcessEvent( evt );
  m_pListLog = nullptr;
}

void ToDoList::BuildModuleMenu( const ModuleType type, wxMenu* menu, M_Code_unused const FileTreeData* data ) {
  if( !menu || !IsAttached() ) {
    return;
  }
  if( type == mtEditorManager ) {
    menu->Append( idAddTodo, _T( "事务" ) );
  }
}

cbConfigurationPanel* ToDoList::GetConfigurationPanel( wxWindow* parent ) {
  ToDoSettingsDlg* dlg = new ToDoSettingsDlg( parent );
  return dlg;
}

void ToDoList::LoadUsers() {
  m_Users.Clear();
  Manager::Get()->GetConfigManager( _T( "todo_list" ) )->Read( _T( "users" ), &m_Users );
  if( m_Users.GetCount() == 0 ) {
    m_Users.Add( wxGetUserId() );
  }
  SaveUsers();
}

void ToDoList::SaveUsers() {
  Manager::Get()->GetConfigManager( _T( "todo_list" ) )->Write( _T( "users" ), m_Users );
}

void ToDoList::LoadTypes() {
  m_Types.Clear();
  Manager::Get()->GetConfigManager( _T( "todo_list" ) )->Read( _T( "types" ), &m_Types );
  if( m_Types.GetCount() == 0 ) {
    m_Types.Add( _T( "TODO" ) );   //备忘录
    m_Types.Add( _T( "@todo" ) );
    m_Types.Add( _T( "\\todo" ) );
    m_Types.Add( _T( "FIXME" ) );   //bug标记
    m_Types.Add( _T( "@fixme" ) );
    m_Types.Add( _T( "\\fixme" ) );
    m_Types.Add( _T( "NOTE" ) );   //笔记
    m_Types.Add( _T( "@note" ) );
    m_Types.Add( _T( "\\note" ) );
  }
  m_pListLog->m_pAllowedTypesDlg->Clear();
  m_pListLog->m_pAllowedTypesDlg->AddItem( m_Types );
  wxArrayString selectedTypes;
  Manager::Get()->GetConfigManager( _T( "todo_list" ) )->Read( _T( "types_selected" ), &selectedTypes );
  if( selectedTypes.GetCount() == 0 ) {
    for( size_t i = 0; i < m_Types.GetCount(); i++ ) {
      selectedTypes.Add( m_Types[i] );
    }
  }
  m_pListLog->m_pAllowedTypesDlg->SetChecked( selectedTypes );
  SaveTypes();
}

void ToDoList::SaveTypes() {
  Manager::Get()->GetConfigManager( _T( "todo_list" ) )->Write( _T( "types" ), m_Types );
}

void ToDoList::OnAppDoneStartup( CodeBlocksEvent& event ) {
  m_InitDone = true;
  Parse();
  event.Skip();
}

void ToDoList::OnUpdateUI( M_Code_unused wxUpdateUIEvent& event ) {
  Manager::Get()->GetAppFrame()->GetMenuBar()->Check( idViewTodo, IsWindowReallyShown( m_pListLog->GetWindow() ) );
}

void ToDoList::OnUpdateAdd( wxUpdateUIEvent& event ) {
  event.Enable( Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor() != NULL );
}

void ToDoList::OnViewList( wxCommandEvent& event ) {
  if( event.IsChecked() ) {
    CodeBlocksLogEvent evtShow( cbEVT_SHOW_LOG_MANAGER );
    Manager::Get()->ProcessEvent( evtShow );
    CodeBlocksLogEvent evt( cbEVT_SWITCH_TO_LOG_WINDOW, m_pListLog );
    Manager::Get()->ProcessEvent( evt );
  } else {
    CodeBlocksLogEvent evt( cbEVT_HIDE_LOG_WINDOW, m_pListLog );
    Manager::Get()->ProcessEvent( evt );
  }
}

void ToDoList::OnAddItem( M_Code_unused wxCommandEvent& event ) {
  M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  if( !ed ) {
    return;
  }
  EditorColourSet* colour_set = Manager::Get()->GetEditorManager()->GetColourSet();
  if( !colour_set ) {
    return;
  }
  HighlightLanguage hlang = colour_set->GetLanguageName( ed->GetLanguage() );
  bool edIsCCpp = ( hlang == _T( "C/C++" ) );
  CommentToken token = colour_set->GetCommentToken( ed->GetLanguage() );
  bool hasStreamComments = not token.streamCommentStart.IsEmpty();
  bool hasLineComments = not token.lineComment.IsEmpty();
  if( !( edIsCCpp || hasLineComments || hasStreamComments ) ) {
    return;
  }
  std::bitset < ( int )tdctError + 1 > supportedTdcts;
  supportedTdcts[tdctLine] = !( token.lineComment.IsEmpty() );
  supportedTdcts[tdctStream] = !( token.streamCommentStart.IsEmpty() );
  supportedTdcts[tdctDoxygenLine] = !( token.doxygenLineComment.IsEmpty() );
  supportedTdcts[tdctDoxygenStream] = !( token.doxygenStreamCommentStart.IsEmpty() );
  supportedTdcts[tdctWarning] = edIsCCpp;
  supportedTdcts[tdctError] = edIsCCpp;
  AddTodoDlg dlg( Manager::Get()->GetAppWindow(), m_Users, m_Types, supportedTdcts );
  PlaceWindow( &dlg );
  if( dlg.ShowModal() != wxID_OK ) {
    return;
  }
  LoadUsers();
  LoadTypes();
  M_Code_StyledTextCtrl* control = ed->GetControl();
  int idx = 0;
  int crlfLen = 0;
  int origPos = control->GetCurrentPos();
  int line = control->GetCurrentLine();
  ToDoCommentType CmtType = dlg.GetCommentType();
  if( dlg.GetPosition() == tdpCurrent ) {
    idx = control->GetCurrentPos();
    if( idx != control->GetLineEndPosition( line ) && ( CmtType != tdctStream ) && ( CmtType != tdctDoxygenStream ) ) {
      wxString streamStart = token.streamCommentStart, streamEnd = token.streamCommentEnd;
      if( CmtType == tdctDoxygenLine && !token.doxygenStreamCommentStart.IsEmpty() ) {
        streamStart = token.doxygenStreamCommentStart;
        streamEnd = token.doxygenStreamCommentEnd;
      }
      AskTypeDlg asktype_dlg( Manager::Get()->GetAppWindow(), streamStart, streamEnd );
      PlaceWindow( &asktype_dlg );
      if( asktype_dlg.ShowModal() != wxID_OK ) {
        return;
      }
      switch( asktype_dlg.GetTypeCorrection() ) {
        case tcStay:
          break;
        case tcSwitch:
          if( CmtType == tdctDoxygenLine ) {
            CmtType = tdctDoxygenStream;
          } else
          { CmtType = tdctStream; }
          break;
        case tcMove:
        default:
          idx = control->GetLineEndPosition( line );
          break;
      }
    }
  } else {
    if( dlg.GetPosition() == tdpAbove ) {
      idx = control->GetLineEndPosition( line - 1 );
    } else if( dlg.GetPosition() == tdpBelow ) {
      idx = control->GetLineEndPosition( line );
    }
    switch( control->GetEOLMode() ) {
      case wxSCI_EOL_CRLF:
        crlfLen = 2;
        break;
      case wxSCI_EOL_CR:
      case wxSCI_EOL_LF:
      default:
        crlfLen = 1;
        break;
    }
    if( idx > 0 ) {
      idx += crlfLen;
    }
  }
  if( idx > control->GetLength() ) {
    idx = control->GetLength();
  }
  wxString buffer;
  switch( CmtType ) {
    default:
    case tdctLine:
      buffer << token.lineComment;
      break;
    case tdctDoxygenLine:
      buffer << token.doxygenLineComment;
      break;
    case tdctDoxygenStream:
      buffer << token.doxygenStreamCommentStart;
      break;
    case tdctWarning:
      buffer << _T( "#warning" );
      break;
    case tdctError:
      buffer << _T( "#error" );
      break;
    case tdctStream:
      buffer << token.streamCommentStart;
      break;
  }
  buffer << _T( " " );
  buffer << dlg.GetType() << _T( " " );
  wxString priority = wxString::Format( _T( "%d" ), dlg.GetPriority() );
  buffer << _T( "(" ) << dlg.GetUser() << _T( "#" ) << priority << _T( "#" ) << ( dlg.DateRequested() ? ( wxDateTime::Today().Format( _T( "%x): " ) ) ) :  _T( "): " ) );
  wxString text = dlg.GetText();
  if( ( CmtType == tdctWarning ) || ( CmtType == tdctError ) ) {
    if( text.Replace( _T( "\r\n" ), _T( "\\\r\n" ) ) == 0 ) {
      text.Replace( _T( "\n" ), _T( "\\\n" ) );
    }
    if( text.Replace( _T( "\\\\\r\n" ), _T( "\\\r\n" ) ) == 0 ) {
      text.Replace( _T( "\\\\\n" ), _T( "\\\n" ) );
    }
  } else if( CmtType == tdctLine || ( CmtType == tdctDoxygenLine ) ) {
    wxString lc;
    if( CmtType == tdctLine ) {
      lc = token.lineComment;
    } else
    { lc = token.doxygenLineComment; }
    if( text.Replace( _T( "\r\n" ), _T( "\r\n" ) + lc ) == 0 ) {
      text.Replace( _T( "\n" ), _T( "\n" ) + lc );
    }
    if( text.Replace( _T( "\r\n" ), _T( " ...\r\n" ), false ) == 0 ) {
      text.Replace( _T( "\n" ), _T( " ...\n" ), false );
    }
  }
  buffer << text;
  if( CmtType == tdctStream ) {
    buffer << _T( " " ) << token.streamCommentEnd;
  }
  if( CmtType == tdctDoxygenStream ) {
    buffer << _T( " " ) << token.doxygenStreamCommentEnd;
  }
  if( dlg.GetPosition() != tdpCurrent ) {
    buffer << _T( "\n" );
  }
  control->InsertText( idx, buffer );
  if( dlg.GetPosition() == tdpAbove ) {
    origPos += buffer.Length() + crlfLen;
  }
  control->GotoPos( origPos );
  control->EnsureCaretVisible();
  ParseCurrent( true );
}

void ToDoList::OnReparse( CodeBlocksEvent& event ) {
  if( m_InitDone && m_AutoRefresh && !( ProjectManager::IsBusy() ) ) {
    Parse();
  } else {
    m_ParsePending = true;
    m_pListLog->Clear();
  }
  event.Skip();
}

void ToDoList::OnReparseCurrent( CodeBlocksEvent& event ) {
  bool forced = ( event.GetEventType() == cbEVT_EDITOR_OPEN || event.GetEventType() == cbEVT_EDITOR_SAVE );
  if( m_InitDone && m_AutoRefresh && !( ProjectManager::IsBusy() ) ) {
    if( m_ParsePending ) {
      m_ParsePending = false;
      Parse();
    } else {
      ParseCurrent( forced );
    }
  }
  event.Skip();
}

void ToDoList::ParseCurrent( bool forced ) {
  m_pListLog->ParseCurrent( forced );
}

void ToDoList::Parse() {
  m_pListLog->Parse();
}
